%% USV_Syntax_Pipeline_PRO_seeded_WITH_LM_PCA_TABLE.m
% - Bout-based transitions, per-mouse Markov
% - Group mean P matrices + heatmap/network figs + vector PDFs
% - Seeded permutation tests (nPerm=50000) + CSV reports
% - PCA + centroid permutation + optional Monte Carlo CI + pca_results.mat
% - Cohen's d effect size for entropy rate & self-transition ratio
% - ADD: Linear model control (entropyRate ~ totalCalls + group) + CSV outputs
% - ADD: PCA Eigenvalue/Explained variance CSV table
%
% Required columns in each CSV:
%   Bout_ID, Class
% Optional:
%   Index_after_filter (if present, used to sort within bout; otherwise keep file order)

clear; clc;
set(groot, 'defaultTextInterpreter', 'none');
set(groot, 'defaultAxesTickLabelInterpreter', 'none');
set(groot, 'defaultLegendInterpreter', 'none');

%% =========================
% Config
%% =========================
dataDir = 'D:\USV_Markov';
outDir  = fullfile(dataDir,'_output_syntax');
if ~exist(outDir,'dir'); mkdir(outDir); end

% Plot threshold for syntax network edges (P >= minProbPlot)
minProbPlot = 0.05;

% Reproducibility
rng(1);

% Permutations
nPerm = 50000;

% Entropy epsilon
eps0 = 1e-12;

% PCA CI printing window
printPcaCI = true;
pcaCIWindow = [0.02, 0.10];

% Top edges output (per group)
topNedges = 50;             % output top N edges in GroupX_topEdges.csv
topEdgeMinP = minProbPlot;  % only consider edges >= this P for topEdges table

% Effect size
% If you want Hedges' g (small sample correction), set useHedgesG=true
useHedgesG = false;

% ===== ADD: Linear model control =====
runLM = true;                 % 是否執行 entropyRate ~ totalCalls + group
runLM_interaction = false;    % 是否加 totalCalls*group 交互作用（通常不必）

%% =========================
% Load CSV files
%% =========================
files = dir(fullfile(dataDir,'*.csv'));
if isempty(files); error('No CSV files found in %s', dataDir); end

allData = table();
mouseMeta = table();
skip = 0;

for i = 1:numel(files)
    fname = files(i).name;

    % group prefix: "<num>_...csv"
    tok = regexp(fname,'^(\d+)_','tokens','once');
    if isempty(tok)
        skip = skip + 1;
        continue;
    end
    groupID = str2double(tok{1});

    fpath = fullfile(dataDir, fname);
    T = robustReadUSVcsv(fpath, fname);

    T.group   = repmat(groupID, height(T), 1);
    T.mouseID = repmat({fname}, height(T), 1);

    allData = [allData; T]; %#ok<AGROW>
    mouseMeta = [mouseMeta; table({fname}, groupID, 'VariableNames', {'mouseID','group'})]; %#ok<AGROW>
end

mouseMeta = unique(mouseMeta, 'rows');
nMouse = height(mouseMeta);

fprintf('\nLoaded %d mice (skipped %d files without numeric prefix).\n', nMouse, skip);

%% =========================
% Define states (cats)
%% =========================
cats = unique(allData.Class, 'stable');
cats = cellstr(string(cats));
K = numel(cats);

groups = unique(mouseMeta.group);
G = numel(groups);

fprintf('Detected %d groups: %s\n', G, mat2str(groups'));
fprintf('Detected %d states (Class).\n\n', K);

%% =========================
% Build per-mouse transition matrices (counts + P)
%% =========================
P_mouse = zeros(K, K, nMouse);
C_mouse = zeros(K, K, nMouse);

selfRatio   = nan(nMouse,1);
entropyRate = nan(nMouse,1);
meanBoutLen = nan(nMouse,1);
totalCalls  = nan(nMouse,1);
totalTrans  = nan(nMouse,1);
nBouts      = nan(nMouse,1);

for m = 1:nMouse
    mid = mouseMeta.mouseID{m};
    subM = allData(strcmp(allData.mouseID, mid), :);

    % housekeeping metrics
    totalCalls(m) = height(subM);
    boutList = unique(subM.Bout_ID);
    nBouts(m) = numel(boutList);

    % build counts by bout (no cross-bout transitions)
    C = zeros(K,K);
    boutLens = [];

    for bi = 1:numel(boutList)
        b = boutList(bi);
        rows = subM(subM.Bout_ID == b, :);

        % sort within bout if Index_after_filter exists
        if ismember('Index_after_filter', rows.Properties.VariableNames)
            rows = sortrows(rows, 'Index_after_filter');
        end

        if height(rows) < 2
            continue;
        end

        boutLens(end+1,1) = height(rows); %#ok<AGROW>

        fromC = rows.Class(1:end-1);
        toC   = rows.Class(2:end);

        fromI = stateIndex(cellstr(string(fromC)), cats);
        toI   = stateIndex(cellstr(string(toC)), cats);

        for t = 1:numel(fromI)
            C(fromI(t), toI(t)) = C(fromI(t), toI(t)) + 1;
        end
    end

    C_mouse(:,:,m) = C;
    totalTrans(m)  = sum(C(:));

    if ~isempty(boutLens)
        meanBoutLen(m) = mean(boutLens);
    else
        meanBoutLen(m) = NaN;
    end

    % P = row-normalized conditional probabilities P(next|current)
    P = zeros(K,K);
    for iS = 1:K
        rs = sum(C(iS,:));
        if rs > 0
            P(iS,:) = C(iS,:) / rs;
        end
    end
    P_mouse(:,:,m) = P;

    if sum(C(:)) > 0
        selfRatio(m) = sum(diag(C)) / sum(C(:));
    end

    entropyRate(m) = entropyRate1(P, C, eps0);
end

%% =========================
% Output: per-mouse metrics CSV
%% =========================
perMouse = table();
perMouse.mouseID         = mouseMeta.mouseID;
perMouse.group           = mouseMeta.group;
perMouse.selfRatio       = selfRatio;
perMouse.entropyRate     = entropyRate;
perMouse.meanBoutLen     = meanBoutLen;
perMouse.totalCalls      = totalCalls;
perMouse.totalTrans      = totalTrans;
perMouse.nBouts          = nBouts;

writetable(perMouse, fullfile(outDir, 'per_mouse_metrics.csv'));

%% =========================
% Output: group summary means CSV
%% =========================
groupSummary = table();
groupSummary.group = groups(:);
groupSummary.GroupCount = zeros(G,1);
groupSummary.mean_selfRatio = zeros(G,1);
groupSummary.mean_entropyRate = zeros(G,1);
groupSummary.mean_meanBoutLen = zeros(G,1);
groupSummary.mean_totalCalls = zeros(G,1);
groupSummary.mean_totalTrans = zeros(G,1);
groupSummary.mean_nBouts = zeros(G,1);

for gi = 1:G
    gVal = groups(gi);
    idxG = (mouseMeta.group == gVal);
    groupSummary.GroupCount(gi)        = sum(idxG);
    groupSummary.mean_selfRatio(gi)    = mean(selfRatio(idxG), 'omitnan');
    groupSummary.mean_entropyRate(gi)  = mean(entropyRate(idxG), 'omitnan');
    groupSummary.mean_meanBoutLen(gi)  = mean(meanBoutLen(idxG), 'omitnan');
    groupSummary.mean_totalCalls(gi)   = mean(totalCalls(idxG), 'omitnan');
    groupSummary.mean_totalTrans(gi)   = mean(totalTrans(idxG), 'omitnan');
    groupSummary.mean_nBouts(gi)       = mean(nBouts(idxG), 'omitnan');
end

writetable(groupSummary, fullfile(outDir, 'group_summary_means.csv'));

%% =========================
% Per-group outputs: Pmean CSV, topEdges CSV, figs + PDFs
%% =========================
for gi = 1:G
    gVal = groups(gi);
    idxG = (mouseMeta.group == gVal);

    Pmean = mean(P_mouse(:,:,idxG), 3, 'omitnan');
    Pmean(~isfinite(Pmean)) = 0;

    % (1) Output GroupX_Pmean.csv
    Tpm = array2table(Pmean, 'VariableNames', matlab.lang.makeValidName(cats));
    Tpm = addvars(Tpm, cats(:), 'Before', 1, 'NewVariableNames', "FromState");
    writetable(Tpm, fullfile(outDir, sprintf('Group%d_Pmean.csv', gVal)));

    % (2) Output GroupX_topEdges.csv
    [ii,jj,pp] = find(Pmean);
    if isempty(pp)
        topEdges = table();
    else
        keep = pp >= topEdgeMinP;
        ii = ii(keep); jj = jj(keep); pp = pp(keep);

        [ppSort, ord] = sort(pp, 'descend');
        ord = ord(1:min(topNedges, numel(ord)));

        ii = ii(ord); jj = jj(ord); pp = ppSort(1:numel(ord));

        topEdges = table();
        topEdges.From = cats(ii);
        topEdges.To   = cats(jj);
        topEdges.P    = pp;
    end
    writetable(topEdges, fullfile(outDir, sprintf('Group%d_topEdges.csv', gVal)));

    % (3) Heatmap
    fig = figure('Color','w','Name',sprintf('Group%d_heatmap',gVal));
    imagesc(Pmean,[0 1]); axis equal tight;
    colormap(flipud(hot)); colorbar;
    title(sprintf('Group %d: Conditional probabilities P(next|current)', gVal), 'FontWeight','normal');

    set(gca,'XTick',1:K,'XTickLabel',cats,...
            'YTick',1:K,'YTickLabel',cats,...
            'TickLabelInterpreter','none',...
            'XAxisLocation','top',...
            'FontSize',10);
    xlabel('End (To)'); ylabel('Start (From)');

    hold on;
    for iS=1:K
        for jS=1:K
            v = Pmean(iS,jS);
            if v > 0
                if v > 0.6, tc=[1 1 1]; else, tc=[0 0 0]; end
                text(jS,iS,sprintf('%.3f',v), 'HorizontalAlignment','center',...
                    'VerticalAlignment','middle','FontSize',8,'Color',tc);
            end
        end
    end
    hold off;

    xlim([0.5 K+0.5]); ylim([0.5 K+0.5]);
    grid on; set(gca,'GridColor',[0.8 0.8 0.8],'GridAlpha',0.5,'LineWidth',0.5);

    savefig(fig, fullfile(outDir, sprintf('Group%d_heatmap.fig', gVal)));
    exportgraphics(fig, fullfile(outDir, sprintf('Group%d_heatmap.pdf', gVal)), 'ContentType','vector');
    close(fig);

    % (4) Syntax network
    A = Pmean;
    A(A < minProbPlot) = 0;
    [ei,ej] = find(A > 0);
    if ~isempty(ei)
        w = double(A(sub2ind(size(A), ei, ej)));
        ei = double(ei(:)); ej = double(ej(:)); w = double(w(:));
        ok = (w > 0) & isfinite(w);

        Gg = digraph(ei(ok), ej(ok), w(ok), cats);

        fig = figure('Color','w','Name',sprintf('Group%d_network',gVal));
        catsLbl = escapeUnderscore(cats);   % 把 '_' 變成 '\_'
p = plot(Gg, 'Layout','circle', 'NodeLabel', catsLbl, 'ArrowSize', 12);
% --- Robust fix for underscore/subscript issue ---
drawnow;

% 抓整個 figure 裡所有 Text（不要只抓 gca）
allTxt = findall(fig, 'Type', 'Text');

% 只改等於 cats 的那些（node labels）
for k = 1:numel(allTxt)
    s = allTxt(k).String;
    if isstring(s); s = char(s); end
    if ischar(s) && any(strcmp(s, cats))
        allTxt(k).Interpreter = 'none';
    end
end
        title(sprintf('Group %d: Syntax network (Probability >= %.2f)', gVal, minProbPlot), ...
              'FontWeight','normal', 'Interpreter','none');
        axis off;

        maxW = max(Gg.Edges.Weight);
        if maxW > 0
            p.LineWidth = 0.5 + 6*(Gg.Edges.Weight/maxW);
        else
            p.LineWidth = 1;
        end

        savefig(fig, fullfile(outDir, sprintf('Group%d_network.fig', gVal)));
        exportgraphics(fig, fullfile(outDir, sprintf('Group%d_network.pdf', gVal)), 'ContentType','vector');
        exportgraphics(fig, fullfile(outDir, sprintf('Group%d_syntaxNetwork.pdf', gVal)), 'ContentType','vector');
        close(fig);
    end
end

%% =========================
% Pairwise permutation tests -> CSV outputs (+ effect size)
%% =========================
fprintf('[Per-mouse] Pairwise permutation on transition patterns (seeded, nPerm=%d):\n', nPerm);
pairTrans = pairPermTransitionPattern(P_mouse, mouseMeta.group, nPerm);
% --- ADD: FDR correction (BH) across pairwise comparisons ---
pairTrans.p_fdr = fdr_bh(pairTrans.p_perm);
pairTrans = movevars(pairTrans, 'p_fdr', 'After', 'p_perm');
writetable(pairTrans, fullfile(outDir, 'pairwise_perm_transitionPattern.csv'));

fprintf('\n[Per-mouse] Pairwise permutation on entropy rate (seeded, nPerm=%d):\n', nPerm);
pairEnt = pairPermMetric(entropyRate, mouseMeta.group, nPerm);

pairEnt_d = pairEffectSize(entropyRate, mouseMeta.group, useHedgesG);
pairEnt = outerjoin(pairEnt, pairEnt_d, 'Keys', {'Group1','Group2'}, 'MergeKeys', true);
pairEnt = movevars(pairEnt, 'effect', 'After', 'p_perm');
pairEnt.Properties.VariableNames{'effect'} = ternary(useHedgesG,'g','d');

fprintf('\n[Effect size] %s for entropy rate:\n', ternary(useHedgesG,'Hedges'' g','Cohen''s d'));
for r = 1:height(pairEnt)
    fprintf('  %d vs %d | %s=%.3f | p=%.5f\n', pairEnt.Group1(r), pairEnt.Group2(r), ...
        ternary(useHedgesG,'g','d'), pairEnt{r, ternary(useHedgesG,'g','d')}, pairEnt.p_perm(r));
end
% --- ADD: FDR correction (BH) across pairwise comparisons ---
pairEnt.p_fdr = fdr_bh(pairEnt.p_perm);
pairEnt = movevars(pairEnt, 'p_fdr', 'After', 'p_perm');
writetable(pairEnt, fullfile(outDir, 'pairwise_perm_entropyRate.csv'));

fprintf('\n[Per-mouse] Pairwise permutation on self-transition ratio (seeded, nPerm=%d):\n', nPerm);
pairSelf = pairPermMetric(selfRatio, mouseMeta.group, nPerm);

pairSelf_d = pairEffectSize(selfRatio, mouseMeta.group, useHedgesG);
pairSelf = outerjoin(pairSelf, pairSelf_d, 'Keys', {'Group1','Group2'}, 'MergeKeys', true);
pairSelf = movevars(pairSelf, 'effect', 'After', 'p_perm');
pairSelf.Properties.VariableNames{'effect'} = ternary(useHedgesG,'g','d');

fprintf('\n[Effect size] %s for self-transition ratio:\n', ternary(useHedgesG,'Hedges'' g','Cohen''s d'));
for r = 1:height(pairSelf)
    fprintf('  %d vs %d | %s=%.3f | p=%.5f\n', pairSelf.Group1(r), pairSelf.Group2(r), ...
        ternary(useHedgesG,'g','d'), pairSelf{r, ternary(useHedgesG,'g','d')}, pairSelf.p_perm(r));
end
% --- ADD: FDR correction (BH) across pairwise comparisons ---
pairSelf.p_fdr = fdr_bh(pairSelf.p_perm);
pairSelf = movevars(pairSelf, 'p_fdr', 'After', 'p_perm');
writetable(pairSelf, fullfile(outDir, 'pairwise_perm_selfRatio.csv'));

%% =========================
% Panel A — Entropy rate scatter + mean ± SEM (+ brackets p & d/g)
%% =========================
fig = figure('Color','w','Name','Entropy_rate');
hold on;

colors = lines(G);

for gi = 1:G
    gVal = groups(gi);
    idx = (mouseMeta.group == gVal);

    y = entropyRate(idx);
    x = gi * ones(sum(idx),1);

    jitter = (rand(size(x)) - 0.5) * 0.2;
    scatter(x + jitter, y, 50, ...
        'MarkerFaceColor', colors(gi,:), ...
        'MarkerEdgeColor','k', ...
        'LineWidth',0.8);

    m = mean(y, 'omitnan');
    sem = std(y,'omitnan') / sqrt(sum(~isnan(y)));
    errorbar(gi, m, sem, 'k', 'LineWidth',1.5, 'CapSize',10);
end

set(gca,'XTick',1:G,'XTickLabel',string(groups), 'FontSize',11);
xlabel('Group');
ylabel('Entropy rate (bits)');
title('Entropy rate per mouse (mean ± SEM)','FontWeight','normal');
box on;

addPairwiseBrackets(gca, pairEnt, groups, entropyRate, mouseMeta.group, useHedgesG);

savefig(fig, fullfile(outDir,'Entropy_rate.fig'));
exportgraphics(fig, fullfile(outDir,'Entropy_rate.pdf'), 'ContentType','vector');
close(fig);

%% =========================
% Panel B — Self-transition ratio scatter + mean ± SEM (+ brackets p & d/g)
%% =========================
fig = figure('Color','w','Name','Self_transition_ratio');
hold on;

colors = lines(G);

for gi = 1:G
    gVal = groups(gi);
    idx = (mouseMeta.group == gVal);

    y = selfRatio(idx);
    x = gi * ones(sum(idx),1);

    jitter = (rand(size(x)) - 0.5) * 0.2;
    scatter(x + jitter, y, 50, ...
        'MarkerFaceColor', colors(gi,:), ...
        'MarkerEdgeColor','k', ...
        'LineWidth',0.8);

    m = mean(y, 'omitnan');
    sem = std(y,'omitnan') / sqrt(sum(~isnan(y)));
    errorbar(gi, m, sem, 'k', 'LineWidth',1.5, 'CapSize',10);
end

set(gca,'XTick',1:G,'XTickLabel',string(groups), 'FontSize',11);
xlabel('Group');
ylabel('Self-transition ratio');
title('Self-transition ratio per mouse (mean ± SEM)', 'FontWeight','normal');
ylim([0 1]);
box on;

addPairwiseBrackets(gca, pairSelf, groups, selfRatio, mouseMeta.group, useHedgesG);

savefig(fig, fullfile(outDir,'Self_transition_ratio.fig'));
exportgraphics(fig, fullfile(outDir,'Self_transition_ratio.pdf'), 'ContentType','vector');
close(fig);

%% =========================
% ADD: Linear model control (entropyRate ~ totalCalls + group)
%% =========================
if runLM
    fprintf('\n[LM] Linear model control for call count:\n');

    tblLM = table(entropyRate, totalCalls, mouseMeta.group, ...
        'VariableNames', {'entropyRate','totalCalls','group'});
    tblLM.group = categorical(tblLM.group);

    ok = isfinite(tblLM.entropyRate) & isfinite(tblLM.totalCalls) & ~ismissing(tblLM.group);
    tblLM = tblLM(ok,:);

    try
        if runLM_interaction
            mdl = fitlm(tblLM, 'entropyRate ~ totalCalls * group');
            modelStr = 'entropyRate ~ totalCalls * group';
        else
            mdl = fitlm(tblLM, 'entropyRate ~ totalCalls + group');
            modelStr = 'entropyRate ~ totalCalls + group';
        end

        fprintf('  Model: %s\n', modelStr);
        disp(mdl);

        coefT = mdl.Coefficients;
        fprintf('\n  Coefficients (Estimate, SE, t, p):\n');
        for i = 1:height(coefT)
            fprintf('    %-12s  % .6f  % .6f  % .4f  p=%.5g\n', ...
                string(coefT.Properties.RowNames{i}), ...
                coefT.Estimate(i), coefT.SE(i), coefT.tStat(i), coefT.pValue(i));
        end

        fprintf('\n  Model fit: R^2=%.3f, adjR^2=%.3f, RMSE=%.3f, F p=%.5g\n', ...
            mdl.Rsquared.Ordinary, mdl.Rsquared.Adjusted, mdl.RMSE, mdl.ModelFitVsNullModel.Pvalue);

        coefOut = coefT;
        coefOut.Term = string(coefT.Properties.RowNames);
        coefOut = movevars(coefOut, 'Term', 'Before', 1);
        writetable(coefOut, fullfile(outDir, 'LM_entropyRate_totalCalls_group_coefficients.csv'));

        an = anova(mdl, 'summary');
        writetable(an, fullfile(outDir, 'LM_entropyRate_totalCalls_group_anova.csv'));

    catch ME
        fprintf('  [LM] fitlm failed. Reason: %s\n', ME.message);
        fprintf('  你可能沒有 Statistics Toolbox。若需要我可以改成不依賴 fitlm 的回歸版本。\n');
    end
end

%% =========================
% PCA + centroid permutation -> pca_results.mat + PCA figs/pdfs
%% =========================
Pvec = reshape(P_mouse, K*K, nMouse)'; % nMouse x (K^2)
X = sqrt(max(Pvec,0));
X = X - mean(X,1);

[coeff, score, latent, tsq, explained, mu] = pca(X);
PC = score(:,1:2);

% ===== ADD: output PCA eigenvalue/explained variance table =====
PCA_table = table((1:length(latent))', latent, explained, cumsum(explained), ...
    'VariableNames', {'PC','Eigenvalue','ExplainedVariance_percent','CumulativeVariance_percent'});
writetable(PCA_table, fullfile(outDir,'PCA_Eigenvalues_ExplainedVariance.csv'));

% centroid max-distance permutation (compute p_pca first)
ug = unique(mouseMeta.group);
centroids = zeros(numel(ug),2);
for g = 1:numel(ug)
    idx = (mouseMeta.group == ug(g));
    centroids(g,:) = mean(PC(idx,:),1);
end

D_obs = 0;
for a=1:numel(ug)
    for b=a+1:numel(ug)
        d = norm(centroids(a,:) - centroids(b,:));
        D_obs = max(D_obs, d);
    end
end

D_perm = zeros(nPerm,1);
for k = 1:nPerm
    permLabel = mouseMeta.group(randperm(nMouse));
    cent = zeros(numel(ug),2);
    for g = 1:numel(ug)
        idx = (permLabel == ug(g));
        cent(g,:) = mean(PC(idx,:),1);
    end
    dmax = 0;
    for a=1:numel(ug)
        for b=a+1:numel(ug)
            d = norm(cent(a,:) - cent(b,:));
            dmax = max(dmax, d);
        end
    end
    D_perm(k) = dmax;
end

k_ge = sum(D_perm >= D_obs);
p_pca = (1 + k_ge) / (nPerm + 1);

fprintf('\n[PCA] Permutation on centroid separation in PC space: p = %.5f (seeded, nPerm=%d)\n', p_pca, nPerm);

pcaCI = [NaN NaN];
if printPcaCI && (p_pca >= pcaCIWindow(1)) && (p_pca <= pcaCIWindow(2))
    [p_lo, p_hi] = binomClopperPearson95(k_ge, nPerm);
    pcaCI = [p_lo p_hi];
    fprintf('[PCA] Monte Carlo 95%% CI for p (Clopper–Pearson): [%.5f, %.5f]\n', p_lo, p_hi);
    fprintf('[PCA] Note: CI reflects Monte Carlo uncertainty given finite permutations.\n');
end

% PCA scatter (simple)
fig = figure('Color','w','Name','PCA');
gscatter(PC(:,1), PC(:,2), mouseMeta.group);
xlabel(sprintf('PC1 (%.1f%%)', explained(1)));
ylabel(sprintf('PC2 (%.1f%%)', explained(2)));
title('PCA of per-mouse transition patterns','FontWeight','normal');
savefig(fig, fullfile(outDir,'PCA.fig'));
exportgraphics(fig, fullfile(outDir,'PCA.pdf'), 'ContentType','vector');
close(fig);

% PCA scatter + centroid + ellipse + p label
fig = figure('Color','w','Name','PCA_PC1_PC2');
hold on;

ugPlot = unique(mouseMeta.group);
colors = lines(numel(ugPlot));

for gi = 1:numel(ugPlot)
    gVal = ugPlot(gi);
    idx = mouseMeta.group == gVal;

hScatter(gi) = scatter(PC(idx,1), PC(idx,2), 60, ...
    'MarkerFaceColor', colors(gi,:), ...
    'MarkerEdgeColor','k'); %#ok<AGROW>

    cent = mean(PC(idx,:),1);
    plot(cent(1), cent(2), 'ko', 'MarkerSize',10, 'MarkerFaceColor','k');

    Xg = PC(idx,:);
    if size(Xg,1) > 2
        mu2 = mean(Xg);
        Sigma = cov(Xg);

        chi2val = 5.991; % 95% CI, df=2
        [V,D] = eig(Sigma);
        t = linspace(0,2*pi,100);
        a = (V*sqrt(D*chi2val))*[cos(t); sin(t)];
        ellipse = a + mu2';

        plot(ellipse(1,:), ellipse(2,:), 'Color', colors(gi,:), 'LineWidth',1.5);
    end
end

xlabel(sprintf('PC1 (%.1f%%)', explained(1)));
ylabel(sprintf('PC2 (%.1f%%)', explained(2)));
title('PCA of per-mouse transition patterns','FontWeight','normal');

text(min(PC(:,1)), max(PC(:,2)), ...
    sprintf('Centroid permutation p = %.3f', p_pca), ...
    'FontSize',10, 'VerticalAlignment','top');
legend(hScatter, "Group " + string(ugPlot), ...
       'Location','best', ...
       'Box','off');
box on;
hold off;
savefig(fig, fullfile(outDir,'PCA_PC1_PC2.fig')); 
exportgraphics(fig, fullfile(outDir,'PCA_PC1_PC2.pdf'), 'ContentType','vector');
close(fig);

% Save pca_results.mat
pcaResults = struct();
pcaResults.Pvec = Pvec;
pcaResults.X = X;
pcaResults.coeff = coeff;
pcaResults.score = score;
pcaResults.explained = explained;
pcaResults.latent = latent;
pcaResults.mu = mu;
pcaResults.PC12 = PC;
pcaResults.groups = mouseMeta.group;
pcaResults.D_obs = D_obs;
pcaResults.D_perm = D_perm;
pcaResults.p_pca = p_pca;
pcaResults.k_ge = k_ge;
pcaResults.nPerm = nPerm;
pcaResults.pcaCI = pcaCI;

save(fullfile(outDir,'pca_results.mat'), '-struct', 'pcaResults');

fprintf('\n=== Outputs written to: %s ===\n', outDir);
%% =========================
% ΔP (Group gA - Group gB) : Publication + Threshold + TopN
%% =========================

gA = 3;     % 要比較的 Group A
gB = 2;     % 要比較的 Group B
thr = 0.05; % threshold 版：只保留 |ΔP| >= thr
TopN = 15;  % TopN 版：只保留 |ΔP| 最大的 N 條

idxA = mouseMeta.group == gA;
idxB = mouseMeta.group == gB;

P_A = mean(P_mouse(:,:,idxA), 3, 'omitnan');
P_B = mean(P_mouse(:,:,idxB), 3, 'omitnan');
DeltaP = P_A - P_B;

%% ---- (1) Publication version (full ΔP, correct diverging cmap, diagonal boxes) ----
fig = figure('Color','w','Name',sprintf('DeltaP_pub_G%d_minus_G%d',gA,gB));
imagesc(DeltaP);
axis equal tight;
hold on;

maxAbs = max(abs(DeltaP(:)));
if ~isfinite(maxAbs) || maxAbs == 0
    maxAbs = 0.01;
end
caxis([-maxAbs maxAbs]);

colormap(bluewhitered(256));  % 0 = white (critical)
cb = colorbar;
ylabel(cb, sprintf('ΔP = P(Group %d) − P(Group %d)', gA, gB), 'FontSize',10, 'Interpreter','none');
title(sprintf('ΔP (Group %d − Group %d)', gA, gB), 'FontWeight','normal','FontSize',12, 'Interpreter','none');

set(gca,'XTick',1:K,'XTickLabel',cats,...
        'YTick',1:K,'YTickLabel',cats,...
        'TickLabelInterpreter','none',...
        'XAxisLocation','top',...
        'FontSize',10);
xlabel('To state'); ylabel('From state');

% 對角線細框（self-transition highlight）
for i = 1:K
    rectangle('Position',[i-0.5, i-0.5, 1, 1], ...
              'EdgeColor',[0 0 0], ...
              'LineWidth',0.5);
end

annotation(fig,'textbox',[0.60 0.90 0.35 0.05],...
    'String',sprintf('Red: increased in Group %d   |   Blue: decreased in Group %d', gA, gA),...
    'EdgeColor','none','FontSize',9);

exportgraphics(fig, fullfile(outDir, sprintf('DeltaP_publication_G%d_minus_G%d.pdf', gA, gB)), 'ContentType','vector');
savefig(fig, fullfile(outDir, sprintf('DeltaP_publication_G%d_minus_G%d.fig', gA, gB)));
close(fig);

%% ---- (2) Threshold version: set small |ΔP| to 0, export important edges CSV ----
DeltaP_thr = DeltaP;
DeltaP_thr(abs(DeltaP_thr) < thr) = 0;

fig = figure('Color','w','Name',sprintf('DeltaP_thr_G%d_minus_G%d',gA,gB));
imagesc(DeltaP_thr);
axis equal tight;
hold on;

maxAbs2 = max(abs(DeltaP_thr(:)));
if ~isfinite(maxAbs2) || maxAbs2 == 0
    maxAbs2 = thr;
end
caxis([-maxAbs2 maxAbs2]);

colormap(bluewhitered(256));
cb = colorbar;
ylabel(cb, sprintf('ΔP (|ΔP| ≥ %.2f)', thr), 'FontSize',10, 'Interpreter','none');

title(sprintf('ΔP (Group %d − Group %d), threshold |ΔP| ≥ %.2f', gA, gB, thr), ...
      'FontWeight','normal','FontSize',12, 'Interpreter','none');

set(gca,'XTick',1:K,'XTickLabel',cats,...
        'YTick',1:K,'YTickLabel',cats,...
        'TickLabelInterpreter','none',...
        'XAxisLocation','top',...
        'FontSize',10);
xlabel('To state'); ylabel('From state');

% 對角線細框
for i = 1:K
    rectangle('Position',[i-0.5, i-0.5, 1, 1], ...
              'EdgeColor',[0 0 0], ...
              'LineWidth',0.5);
end

exportgraphics(fig, fullfile(outDir, sprintf('DeltaP_thresholded_G%d_minus_G%d_thr%.2f.pdf', gA, gB, thr)), 'ContentType','vector');
savefig(fig, fullfile(outDir, sprintf('DeltaP_thresholded_G%d_minus_G%d_thr%.2f.fig', gA, gB, thr)));
close(fig);

% 重要 edges 清單（threshold）
[ii,jj] = find(abs(DeltaP) >= thr);
vals = DeltaP(sub2ind(size(DeltaP), ii, jj));
[~,ord] = sort(abs(vals), 'descend');
ii = ii(ord); jj = jj(ord); vals = vals(ord);

Timp = table(cats(ii), cats(jj), vals, abs(vals), ...
    'VariableNames', {'From','To','DeltaP','AbsDeltaP'});
writetable(Timp, fullfile(outDir, sprintf('DeltaP_importantEdges_G%d_minus_G%d_thr%.2f.csv', gA, gB, thr)));

%% ---- (3) TopN version: keep only TopN |ΔP| entries, export TopN edges CSV ----
absD = abs(DeltaP(:));
[~,ord2] = sort(absD, 'descend');
ord2 = ord2(1:min(TopN, numel(ord2)));

DeltaP_top = zeros(size(DeltaP));
DeltaP_top(ord2) = DeltaP(ord2);

fig = figure('Color','w','Name',sprintf('DeltaP_top%d_G%d_minus_G%d',TopN,gA,gB));
imagesc(DeltaP_top);
axis equal tight;
hold on;

maxAbs3 = max(abs(DeltaP_top(:)));
if ~isfinite(maxAbs3) || maxAbs3 == 0
    maxAbs3 = max(abs(DeltaP(:)));
end
if ~isfinite(maxAbs3) || maxAbs3 == 0
    maxAbs3 = 0.01;
end
caxis([-maxAbs3 maxAbs3]);

colormap(bluewhitered(256));
cb = colorbar;
ylabel(cb, sprintf('\\DeltaP (Top %d)', TopN), 'FontSize',10);

title(sprintf('\\DeltaP (Group %d - Group %d), Top %d |\\DeltaP| transitions', gA, gB, TopN), ...
      'FontWeight','normal','FontSize',12);

set(gca,'XTick',1:K,'XTickLabel',cats,...
        'YTick',1:K,'YTickLabel',cats,...
        'TickLabelInterpreter','none',...
        'XAxisLocation','top',...
        'FontSize',10);
xlabel('To state'); ylabel('From state');

% 對角線細框
for i = 1:K
    rectangle('Position',[i-0.5, i-0.5, 1, 1], ...
              'EdgeColor',[0 0 0], ...
              'LineWidth',0.5);
end

exportgraphics(fig, fullfile(outDir, sprintf('DeltaP_Top%d_G%d_minus_G%d.pdf', TopN, gA, gB)), 'ContentType','vector');
savefig(fig, fullfile(outDir, sprintf('DeltaP_Top%d_G%d_minus_G%d.fig', TopN, gA, gB)));
close(fig);

% TopN edges 清單
[ii2,jj2] = ind2sub(size(DeltaP), ord2);
vals2 = DeltaP(ord2);
Ttop = table(cats(ii2), cats(jj2), vals2, abs(vals2), ...
    'VariableNames', {'From','To','DeltaP','AbsDeltaP'});
writetable(Ttop, fullfile(outDir, sprintf('DeltaP_Top%d_edges_G%d_minus_G%d.csv', TopN, gA, gB)));
%% ===== Differential Network (ΔP) =====

fig = figure('Color','w','Position',[200 200 700 700]);
hold on

n = size(DeltaP,1);
theta = linspace(0,2*pi,n+1);
theta(end) = [];

x = cos(theta);
y = sin(theta);

% 畫 node
scatter(x,y,600,'k','filled')

% node labels
for i = 1:n
    text(1.15*x(i),1.15*y(i),cats{i},...
        'HorizontalAlignment','center',...
        'Interpreter','none',...
        'FontSize',10);
end

% 畫 edge
for i = 1:n
    for j = 1:n
        if abs(DeltaP(i,j)) >= thr

            % 起點終點
            x1 = x(i); y1 = y(i);
            x2 = x(j); y2 = y(j);

            % 顏色
            if DeltaP(i,j) > 0
                col = [0.8 0 0];   % red
            else
                col = [0 0 0.8];   % blue
            end

            % 粗細
            lw = 5 * abs(DeltaP(i,j));

            plot([x1 x2],[y1 y2],...
                'Color',col,...
                'LineWidth',lw);
        end
    end
end

axis equal off
title(sprintf('Differential syntax network (Group %d - Group %d)',gA,gB),...
      'Interpreter','none')
% ---- Save and close ----
exportgraphics(fig, ...
    fullfile(outDir, sprintf('DeltaP_network_G%d_minus_G%d.pdf', gA, gB)), ...
    'ContentType','vector');

savefig(fig, ...
    fullfile(outDir, sprintf('DeltaP_network_G%d_minus_G%d.fig', gA, gB)));

close(fig);

%% ============================================================
% FUNCTIONS
%% ============================================================
function idx = stateIndex(classCell, cats)
idx = zeros(numel(classCell),1);
for i = 1:numel(classCell)
    idx(i) = find(strcmp(classCell{i}, cats), 1);
end
end

function H = entropyRate1(P, C, eps0)
tot = sum(C(:));
if tot <= 0
    H = NaN;
    return;
end
pi = sum(C,2) / tot;

Hi = zeros(size(P,1),1);
for i = 1:size(P,1)
    row = P(i,:);
    nz = row > 0;
    Hi(i) = -sum(row(nz) .* log2(row(nz) + eps0));
end
H = sum(pi .* Hi);
end

function T = pairPermTransitionPattern(P_mouse, group, nPerm)
ug = unique(group);
nPairs = nchoosek(numel(ug), 2);

res = table('Size',[nPairs 6], ...
    'VariableTypes', {'double','double','double','double','double','double'}, ...
    'VariableNames', {'Group1','Group2','nA','nB','T_obs','p_perm'});

row = 0;
for a = 1:numel(ug)
    for b = a+1:numel(ug)
        row = row + 1;

        idxA = find(group == ug(a));
        idxB = find(group == ug(b));
        nA = numel(idxA); nB = numel(idxB);

        vA = mean(P_mouse(:,:,idxA), 3, 'omitnan');
        vB = mean(P_mouse(:,:,idxB), 3, 'omitnan');
        T_obs = sum((vA(:) - vB(:)).^2);

        idxAll = [idxA; idxB];
        T_perm = zeros(nPerm,1);

        for k = 1:nPerm
            perm = idxAll(randperm(numel(idxAll)));
            pA = perm(1:nA);
            pB = perm(nA+1:end);

            vAp = mean(P_mouse(:,:,pA), 3, 'omitnan');
            vBp = mean(P_mouse(:,:,pB), 3, 'omitnan');
            T_perm(k) = sum((vAp(:) - vBp(:)).^2);
        end

        pval = (1 + sum(T_perm >= T_obs)) / (nPerm + 1);

        fprintf('  %d vs %d | nA=%d nB=%d | T=%.5f | p=%.5f\n', ug(a), ug(b), nA, nB, T_obs, pval);

        res.Group1(row) = ug(a);
        res.Group2(row) = ug(b);
        res.nA(row)     = nA;
        res.nB(row)     = nB;
        res.T_obs(row)  = T_obs;
        res.p_perm(row) = pval;
    end
end
T = res;
end

function T = pairPermMetric(metric, group, nPerm)
ug = unique(group);
nPairs = nchoosek(numel(ug), 2);

res = table('Size',[nPairs 6], ...
    'VariableTypes', {'double','double','double','double','double','double'}, ...
    'VariableNames', {'Group1','Group2','nA','nB','T_obs','p_perm'});

row = 0;
for a = 1:numel(ug)
    for b = a+1:numel(ug)
        row = row + 1;

        idxA = find(group == ug(a));
        idxB = find(group == ug(b));
        nA = numel(idxA); nB = numel(idxB);

        T_obs = abs(mean(metric(idxA), 'omitnan') - mean(metric(idxB), 'omitnan'));

        idxAll = [idxA; idxB];
        T_perm = zeros(nPerm,1);

        for k = 1:nPerm
            perm = idxAll(randperm(numel(idxAll)));
            pA = perm(1:nA);
            pB = perm(nA+1:end);

            T_perm(k) = abs(mean(metric(pA), 'omitnan') - mean(metric(pB), 'omitnan'));
        end

        pval = (1 + sum(T_perm >= T_obs)) / (nPerm + 1);

        fprintf('  %d vs %d | nA=%d nB=%d | T=%.5f | p=%.5f\n', ug(a), ug(b), nA, nB, T_obs, pval);

        res.Group1(row) = ug(a);
        res.Group2(row) = ug(b);
        res.nA(row)     = nA;
        res.nB(row)     = nB;
        res.T_obs(row)  = T_obs;
        res.p_perm(row) = pval;
    end
end
T = res;
end

function T = pairEffectSize(metric, group, useHedgesG)
ug = unique(group);
nPairs = nchoosek(numel(ug), 2);

T = table('Size',[nPairs 5], ...
    'VariableTypes', {'double','double','double','double','double'}, ...
    'VariableNames', {'Group1','Group2','nA','nB','effect'});

row = 0;
for a = 1:numel(ug)
    for b = a+1:numel(ug)
        row = row + 1;

        xA = metric(group == ug(a)); xA = xA(isfinite(xA));
        xB = metric(group == ug(b)); xB = xB(isfinite(xB));

        nA = numel(xA); nB = numel(xB);
        mA = mean(xA);  mB = mean(xB);
        sA = std(xA, 0);
        sB = std(xB, 0);

        sp = sqrt(((nA-1)*sA^2 + (nB-1)*sB^2) / max(nA+nB-2, 1));

        if sp == 0 || ~isfinite(sp)
            eff = NaN;
        else
            d = (mA - mB) / sp;
            if useHedgesG
                J = 1 - (3 / (4*(nA+nB) - 9));
                eff = J * d;
            else
                eff = d;
            end
        end

        T.Group1(row) = ug(a);
        T.Group2(row) = ug(b);
        T.nA(row)     = nA;
        T.nB(row)     = nB;
        T.effect(row) = eff;
    end
end
end

function addPairwiseBrackets(ax, pairTable, groups, metric, groupLabels, useHedgesG)
axes(ax); %#ok<LAXES>
hold on;

G = numel(groups);
xMap = containers.Map('KeyType','double','ValueType','double');
for gi = 1:G
    xMap(groups(gi)) = gi;
end

yAll = metric(isfinite(metric));
if isempty(yAll)
    return;
end
yMin = min(yAll);
yMax = max(yAll);
yr = yMax - yMin;
if yr <= 0
    yr = 1;
end

base = yMax + 0.08*yr;
step = 0.08*yr;

span = zeros(height(pairTable),1);
for r=1:height(pairTable)
    if ~isKey(xMap, pairTable.Group1(r)) || ~isKey(xMap, pairTable.Group2(r))
        span(r) = Inf;
    else
        span(r) = abs(xMap(pairTable.Group2(r)) - xMap(pairTable.Group1(r)));
    end
end
[~, ord] = sort(span, 'ascend');

effName = ternary(useHedgesG,'g','d');

for ii = 1:numel(ord)
    r = ord(ii);

    g1 = pairTable.Group1(r);
    g2 = pairTable.Group2(r);
    if ~isKey(xMap, g1) || ~isKey(xMap, g2)
        continue;
    end

    x1 = xMap(g1);
    x2 = xMap(g2);
    if x1 == x2, continue; end
    if x1 > x2, tmp=x1; x1=x2; x2=tmp; end

    p = pairTable.p_perm(r);
    eff = pairTable{r, effName};

    if ~isfinite(p)
        stars = '';
    elseif p < 0.001
        stars = '***';
    elseif p < 0.01
        stars = '**';
    elseif p < 0.05
        stars = '*';
    else
        stars = 'n.s.';
    end

    y = base + (ii-1)*step;
    plot([x1 x1 x2 x2], [y y+0.02*yr y+0.02*yr y], 'k-', 'LineWidth', 1);

    if isfinite(eff)
        lab = sprintf('%s  p=%.3g  %s=%.2f', stars, p, effName, eff);
    else
        lab = sprintf('%s  p=%.3g  %s=NaN', stars, p, effName);
    end
    text(mean([x1 x2]), y+0.025*yr, lab, 'HorizontalAlignment','center', ...
        'VerticalAlignment','bottom', 'FontSize', 9, 'Color', 'k');
end

yl = ylim;
newTop = base + (numel(ord)+1)*step;
if yl(2) < newTop
    ylim([yl(1) newTop]);
end

hold off;
end

function T = robustReadUSVcsv(fpath, fnameForMsg)
T = readtable(fpath);

vars0 = string(T.Properties.VariableNames);
readFailed = all(startsWith(vars0, "Var"));

if readFailed
    opts = detectImportOptions(fpath, 'Delimiter', ',');
    opts.VariableNamesLine = 1;
    opts.DataLines = [2 Inf];
    T = readtable(fpath, opts);
end

T.Properties.VariableNames = matlab.lang.makeValidName(strtrim(T.Properties.VariableNames));

required = {'Bout_ID','Class'};
if ~all(ismember(required, T.Properties.VariableNames))
    missing = required(~ismember(required, T.Properties.VariableNames));
    error('File %s missing required columns: %s', fnameForMsg, strjoin(missing, ', '));
end
end

function [lo, hi] = binomClopperPearson95(k, n)
alpha = 0.05;
if k == 0
    lo = 0;
    hi = 1 - (alpha/2)^(1/n);
elseif k == n
    lo = (alpha/2)^(1/n);
    hi = 1;
else
    lo = betainv(alpha/2, k, n-k+1);
    hi = betainv(1-alpha/2, k+1, n-k);
end
end

function out = ternary(cond, a, b)
if cond
    out = a;
else
    out = b;
end
end
function cmap = redbluecmap(m)
if nargin < 1
    m = size(get(gcf,'colormap'),1);
end

bottom = [0 0 1];
botmiddle = [0.5 0.5 1];
topmiddle = [1 0.5 0.5];
top = [1 0 0];

cmap = zeros(m,3);
for i = 1:m
    t = (i-1)/(m-1);
    if t < 0.5
        cmap(i,:) = bottom*(1-2*t) + botmiddle*(2*t);
    else
        cmap(i,:) = topmiddle*(2*(t-0.5)) + top*(1-2*(t-0.5));
    end
end
end
% Create symmetric blue-white-red
half = floor(m/2);

% Blue to white
r1 = linspace(0,1,half)';
g1 = linspace(0,1,half)';
b1 = ones(half,1);

% White to red
r2 = ones(half,1);
g2 = linspace(1,0,half)';
b2 = linspace(1,0,half)';

cmap = [r1 g1 b1; r2 g2 b2];

function cmap = bluewhitered(m)
% Blue-White-Red diverging colormap with WHITE exactly at 0.
% Negative -> blue, zero -> white, positive -> red.

if nargin < 1
    m = 256;
end

% Make m odd so the exact midpoint is a single row (pure white)
if mod(m,2) == 0
    m = m + 1;
end

half = (m-1)/2;

% blue -> white (include white)
b2w = [linspace(0,1,half+1)'  linspace(0,1,half+1)'  ones(half+1,1)];

% white -> red (include white)
w2r = [ones(half+1,1)        linspace(1,0,half+1)'  linspace(1,0,half+1)'];

% merge without duplicating white row
cmap = [b2w; w2r(2:end,:)];
end
function out = escapeUnderscore(in)
% Convert labels so '_' is shown literally under TeX interpreter.
% Works for string/cellstr/categorical.

if iscategorical(in)
    in = cellstr(in);
elseif isstring(in)
    in = cellstr(in);
elseif ischar(in)
    in = {in};
end

out = in;
for i = 1:numel(out)
    if ismissing(out{i}); out{i} = ''; continue; end
    out{i} = strrep(out{i}, '_', '\_');
end
end